#include "PreCompile.hpp"
#include "NamedObject.hpp"

namespace BuildNG
{
  ///////////
  // NamedObjectCollectionBase
  NamedObjectCollectionBase::NamedObjectCollectionBase(int flags)
    : flags(flags)
  {
    collection_name = "<unnamed collection>";
    item_type_name = "item";
  }

  NamedObjectCollectionBase::NamedObjectCollectionBase(const Glib::ustring collection_name,
    const Glib::ustring &item_type_name,int flags)
    : collection_name(collection_name),
    item_type_name(item_type_name),
    flags(flags)
  {
  }

  NamedObjectCollectionBase::~NamedObjectCollectionBase()
  {
    for(ByNameMapIt it = by_name.begin();it != by_name.end();++it)
    {
      it->second.pre_name_change_connection.disconnect();
      it->second.post_name_change_connection.disconnect();
    }
  }

  void NamedObjectCollectionBase::add(Glib::RefPtr<INamedObject> obj)
  {
    Item item;
    item.obj = obj;

    /*if(flags & Flag_AllowEmptyName

    const Glib::ustring &name = obj->get_name();
    if(flags & Flag_ErrorOnSurroundingWhitespace &&
      (is_whitespace(name.c_str()) || is_whitespace(name.c_str() + name.bytes() - 1))
    {
      throw InvalidNameException(collection_name +
        " doesn't allow names with surrounding whitespace.");
    }*/

    std::pair<ByNameMapIt,bool> insert_res =
      by_name.insert(std::make_pair(obj->get_name(),item));
    if(!insert_res.second)
    {
      throw NameCollisionException(collection_name +
        " already contains a " + item_type_name + " called \"" + obj->get_name() + "\".");
    }

    Item &cur_item = insert_res.first->second;
    cur_item.pre_name_change_connection = obj->pre_name_change.connect(
      sigc::mem_fun(this,&NamedObjectCollectionBase::pre_name_change_callback));
    cur_item.post_name_change_connection = obj->post_name_change.connect(
      sigc::mem_fun(this,&NamedObjectCollectionBase::post_name_change_callback));
  }

  int NamedObjectCollectionBase::get_size() const
  {
    return (int)by_name.size();
  }

  Glib::RefPtr<INamedObject> NamedObjectCollectionBase::try_get(const Glib::ustring &name)
  {
    ByNameMapIt it = by_name.find(name);
    if(it != by_name.end())
      return it->second.obj;
    else
      return Glib::RefPtr<INamedObject>();
  }

  void NamedObjectCollectionBase::set_collection_name(const Glib::ustring &name)
  {
    collection_name = name;
  }

  void NamedObjectCollectionBase::set_item_type_name(const Glib::ustring &name)
  {
    item_type_name = name;
  }

  void NamedObjectCollectionBase::pre_name_change_callback(
    Glib::RefPtr<INamedObject> obj,const Glib::ustring &new_name)
  {
    // see if the name is available
    ByNameMapIt found = by_name.find(new_name);
    if(found != by_name.end() && obj != found->second.obj)
    {
      throw NameCollisionException(collection_name +
        " already contains a " + item_type_name + " called \"" + new_name + "\".");
    }
  }

  void NamedObjectCollectionBase::post_name_change_callback(Glib::RefPtr<INamedObject> obj,
    const Glib::ustring &old_name,const Glib::ustring &new_name)
  {
    assert(by_name.find(old_name) != by_name.end());

#ifndef NDEBUG
    ByNameMapIt found = by_name.find(new_name);
    assert(found == by_name.end() || obj == found->second.obj);
#endif

    // rename the entry in the collection
    ByNameMapIt old_it = by_name.find(old_name);
    by_name[new_name] = old_it->second;
    by_name.erase(old_it);
  }

  //////////
  // NamedObjectCollectionBase::IteratorBase
  NamedObjectCollectionBase::IteratorBase::IteratorBase(NamedObjectCollectionBase *collection)
  {
    initial = true;
    this->collection = collection;
  }

  bool NamedObjectCollectionBase::IteratorBase::move_next()
  {
    assert(initial || it != collection->by_name.end());
    if(initial)
    {
      it = collection->by_name.begin();
      initial = false;
    }
    else
      it++;

    return it != collection->by_name.end();
  }

  void NamedObjectCollectionBase::IteratorBase::reset()
  {
    initial = true;
  }

  Glib::RefPtr<INamedObject> NamedObjectCollectionBase::IteratorBase::cur()
  {
    assert(!initial && it != collection->by_name.end());
    return it->second.obj;
  }
}
